package com.sanweibook.jhook.common.tool.excel;

import com.google.common.collect.Maps;
import com.sanweibook.jhook.common.exception.JhookException;
import com.sanweibook.jhook.common.tool.excel.annotation.Excel;
import com.sanweibook.jhook.common.tool.excel.annotation.ExcelColumn;
import com.sanweibook.jhook.common.tool.excel.handler.ExcelHandler;
import com.sanweibook.jhook.common.tool.excel.support.ExcelConfigure;
import com.sanweibook.jhook.common.tool.excel.support.ExcelContext;
import com.sanweibook.jhook.common.util.FileHandlerUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.util.CellRangeAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Created by twg on 2017/5/29.
 */
public abstract class DefaultExcelWriteHandler<T> implements ExcelHandler {
    private static final Logger log = LoggerFactory.getLogger( DefaultExcelWriteHandler.class );
    private static final String FOLDER_NAME = "excel";

    @Override
    public String writeExcel(List list, String fileName) {
        return generateExcel( list, FOLDER_NAME, fileName );
    }

    @Override
    public String writeExcel(Class clazz, String fileName) {
        return generateExcel( clazz, FOLDER_NAME, fileName );
    }

    @Override
    public void writeExcel(Class clazz, OutputStream outputStream) {

    }

    @Override
    public List readExcel(Class clazz, String fileName) {
        return null;
    }

    private <T> String generateExcel(final Class clazz, final String folderName, final String fileName) {
        Map<Integer, ExcelContext> contextMap = Maps.newHashMap();
        /*是否要转换角色（list -> subList）*/
        boolean flag = setExcelContext( clazz, contextMap );

         /*创建工作蒲*/
        HSSFWorkbook workbook = new HSSFWorkbook();
        /*创建工作表*/
        HSSFSheet sheet = workbook.createSheet();
        HSSFHeader _header = sheet.getHeader();
        _header.setCenter( contextMap.get( 0 ).getTitle() );
        /*设置工作表列宽度*/
        sheet.setDefaultColumnWidth( 20 );
        workbook.setSheetName( 0, "工作表1" );
        /*设置表格背景色*/
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        HSSFDataFormat format = workbook.createDataFormat();
        cellStyle.setAlignment( CellStyle.ALIGN_CENTER );
        cellStyle.setFillForegroundColor( (short) 13 );
        cellStyle.setFillPattern( HSSFCellStyle.SOLID_FOREGROUND );
        cellStyle.setDataFormat( format.getFormat( "@" ) );
        /*创建工作表头*/
        HSSFRow header = sheet.createRow( 0 );
        for (Map.Entry<Integer, ExcelContext> integerExcelContextEntry : contextMap.entrySet()) {
            ExcelContext context = integerExcelContextEntry.getValue();
            HSSFCell cell = header.createCell( integerExcelContextEntry.getKey() );
            cell.setCellValue( context.getColumnName() );
            cell.setCellStyle( cellStyle );
            cell.setCellType( Cell.CELL_TYPE_STRING );
        }
        /*填充工作表内容开始*/
        HSSFCellStyle _cellStyle = workbook.createCellStyle();

        if (flag) {
            List<List<Map<Integer, Object>>> contexts = callbackListInList();
            if (CollectionUtils.isNotEmpty( contexts )) {
                for (int i = 0, r = 1; i < contexts.size(); i++) {
                    List<Map<Integer, Object>> _contexts = contexts.get( i );
                    //合并单元格
                    for (Map.Entry<Integer, ExcelContext> contextEntry : contextMap.entrySet()) {
                        ExcelContext context = contextEntry.getValue();
                        if (context.isMerge()) {
                            CellRangeAddress cellRangeAddress = new CellRangeAddress( r, r + _contexts.size() - 1, context.getColumnIndex(), context.getColumnIndex() );
                            sheet.addMergedRegion( cellRangeAddress );
                        }
                    }
                    for (int j = 0, k = r; j < _contexts.size(); j++, r++) {
                        Map<Integer, Object> objectMap = _contexts.get( j );
                        ExcelConfigure configure = generateExcelConfigure( contextMap, sheet, _cellStyle, format, r, objectMap );
                        setCellValue( configure );
                    }
                }
            }
        } else {
            List<Map<Integer, Object>> contexts = callbackList();
            if (CollectionUtils.isNotEmpty( contexts )) {
                for (int i = 0, r = 1; i < contexts.size(); i++, r++) {
                    Map<Integer, Object> objectMap = contexts.get( i );
                    ExcelConfigure configure = generateExcelConfigure( contextMap, sheet, _cellStyle, format, r, objectMap );
                    setCellValue( configure );
                }
            }
        }

        String filePath = FileHandlerUtils.generateFilePath( folderName );
        FileOutputStream out = null;
        try {
            File file = new File( filePath );
            if (!file.exists()) {
                file.mkdirs();
            }
            out = new FileOutputStream( new File( filePath + fileName ) );
            workbook.write( out );
            out.close();
        } catch (FileNotFoundException e) {
            log.error( "file name {} not found.", fileName, e );
        } catch (IOException e) {
            log.error( "workbook write error.", e );
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    log.error( "file output stream close error.", e );
                }
            }
        }
        return filePath;

    }


    public <T> String generateExcel(final List<T> list, final String folderName, final String fileName) {
        Class clazz = list.get( 0 ).getClass();
        Map<Integer, ExcelContext> contextMap = Maps.newHashMap();
        /*是否要转换角色（list -> subList）*/
        boolean flag = setExcelContext( clazz, contextMap );

         /*创建工作蒲*/
        HSSFWorkbook workbook = new HSSFWorkbook();
        /*创建工作表*/
        HSSFSheet sheet = workbook.createSheet();
        HSSFHeader _header = sheet.getHeader();
        _header.setCenter( contextMap.get( 0 ).getTitle() );
        /*设置工作表列宽度*/
        sheet.setDefaultColumnWidth( 20 );
        workbook.setSheetName( 0, "工作表1" );
        /*设置表格背景色*/
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        HSSFDataFormat format = workbook.createDataFormat();
        cellStyle.setAlignment( CellStyle.ALIGN_CENTER );
        cellStyle.setFillForegroundColor( (short) 13 );
        cellStyle.setFillPattern( HSSFCellStyle.SOLID_FOREGROUND );
        cellStyle.setDataFormat( format.getFormat( "@" ) );
        /*创建工作表头*/
        HSSFRow header = sheet.createRow( 0 );
        for (Map.Entry<Integer, ExcelContext> integerExcelContextEntry : contextMap.entrySet()) {
            ExcelContext context = integerExcelContextEntry.getValue();
            HSSFCell cell = header.createCell( integerExcelContextEntry.getKey() );
            cell.setCellValue( context.getColumnName() );
            cell.setCellStyle( cellStyle );
            cell.setCellType( Cell.CELL_TYPE_STRING );
        }
        /*填充工作表内容开始*/
        HSSFCellStyle _cellStyle = workbook.createCellStyle();

        if (flag) {
            List<List<Map<Integer, Object>>> contexts = callbackListInList();
            for (int i = 0, r = 1; i < contexts.size(); i++) {
                List<Map<Integer, Object>> _contexts = contexts.get( i );
                //合并单元格
                for (Map.Entry<Integer, ExcelContext> contextEntry : contextMap.entrySet()) {
                    ExcelContext context = contextEntry.getValue();
                    if (context.isMerge()) {
                        CellRangeAddress cellRangeAddress = new CellRangeAddress( r, r + _contexts.size() - 1, context.getColumnIndex(), context.getColumnIndex() );
                        sheet.addMergedRegion( cellRangeAddress );
                    }
                }
                for (int j = 0, k = r; j < _contexts.size(); j++, r++) {
                    Map<Integer, Object> objectMap = _contexts.get( j );
                    ExcelConfigure configure = generateExcelConfigure( contextMap, sheet, _cellStyle, format, r, objectMap );
                    setCellValue( configure );
                }
            }
        } else {
            List<Map<Integer, Object>> contexts = callbackList();
            for (int i = 0, r = 1; i < contexts.size(); i++, r++) {
                Map<Integer, Object> objectMap = contexts.get( i );
                ExcelConfigure configure = generateExcelConfigure( contextMap, sheet, _cellStyle, format, r, objectMap );
                setCellValue( configure );
            }
        }

        String filePath = FileHandlerUtils.generateFilePath( folderName );
        FileOutputStream out = null;
        try {
            File file = new File( filePath );
            if (!file.exists()) {
                file.mkdirs();
            }
            out = new FileOutputStream( new File( filePath + fileName ) );
            workbook.write( out );
            out.close();
        } catch (FileNotFoundException e) {
            log.error( "file name {} not found.", fileName, e );
        } catch (IOException e) {
            log.error( "workbook write error.", e );
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    log.error( "file output stream close error.", e );
                }
            }
        }
        return filePath;
    }


    /**
     * 生成列中多行复杂excel
     *
     * @param <T>
     * @return
     */
    protected abstract <T> List<List<Map<Integer, Object>>> callbackListInList();

    /**
     * 生成列中单行excel
     *
     * @param <T>
     * @return
     */
    protected abstract <T> List<Map<Integer, Object>> callbackList();


    private void setCellValue(ExcelConfigure configure) {
        HSSFCellStyle cellStyle = configure.getCellStyle();
        HSSFDataFormat format = configure.getDataFormat();
        Map<Integer, Object> objectMap = configure.getValueMap();
        Map<Integer, ExcelContext> contextMap = configure.getContextMap();
        HSSFRow row = configure.getSheet().createRow( configure.getRowNum() );
        for (Map.Entry<Integer, ExcelContext> entry : contextMap.entrySet()) {
            ExcelContext context = entry.getValue();
            HSSFCell cell = row.createCell( entry.getKey() );
            cellStyle.setDataFormat(format.getFormat("@"));
            if (objectMap.containsKey( entry.getKey() ) && objectMap.get( entry.getKey() ) instanceof Date) {
                cell.setCellValue( DateFormatUtils.format( (Date) objectMap.get( entry.getKey() ), context.getDataFormat() ) );
            } else if (objectMap.containsKey( entry.getKey() ) && objectMap.get( entry.getKey() ) instanceof String) {
                cell.setCellValue( (String) objectMap.get( entry.getKey() ) );
            } else if (objectMap.containsKey( entry.getKey() ) &&
                    (objectMap.get( entry.getKey() ) instanceof Double ||
                            objectMap.get( entry.getKey() ) instanceof BigDecimal ||
                            objectMap.get( entry.getKey() ) instanceof Float)) {
                cell.setCellValue( Double.parseDouble( objectMap.get( entry.getKey() ).toString() ) );
            } else if (objectMap.containsKey( entry.getKey() ) && objectMap.get( entry.getKey() ) instanceof Integer) {
                cell.setCellValue( (Integer) objectMap.get( entry.getKey() ) );
            }
            cell.setCellStyle( cellStyle );
            cell.setCellType( Cell.CELL_TYPE_STRING );
        }
    }

    /**
     * @param contextMap
     * @param sheet
     * @param cellStyle
     * @param format
     * @param rowNum
     * @param objectMap
     * @return
     */
    private ExcelConfigure generateExcelConfigure(Map<Integer, ExcelContext> contextMap, HSSFSheet sheet, HSSFCellStyle cellStyle, HSSFDataFormat format, int rowNum, Map<Integer, Object> objectMap) {
        ExcelConfigure configure = new ExcelConfigure();
        configure.setSheet( sheet );
        configure.setContextMap( contextMap );
        configure.setCellStyle( cellStyle );
        configure.setDataFormat( format );
        configure.setRowNum( rowNum );
        configure.setValueMap( objectMap );
        return configure;
    }

    private static boolean setExcelContext(Class clazz, Map<Integer, ExcelContext> contextMap) {
        boolean flag = false;
        if (clazz.isAnnotationPresent( Excel.class )) {
            Excel excel = (Excel) clazz.getAnnotation( Excel.class );
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent( ExcelColumn.class )) {
                    ExcelColumn excelColumn = field.getAnnotation( ExcelColumn.class );
                    if (!excelColumn.isShow() || (StringUtils.isBlank( excelColumn.columnName() ) && !excelColumn.weight())) {
                        continue;
                    }
                    ExcelContext excelContext = new ExcelContext();
                    excelContext.setTitle( excel.title() );
                    excelContext.setColumnIndex( excelColumn.columnIndex() );
                    excelContext.setColumnName( excelColumn.columnName() );
                    excelContext.setDataFormat( excelColumn.dataFormat() );
                    excelContext.setMerge( excelColumn.isMerge() );
                    if (contextMap.containsKey( excelColumn.columnIndex() )) {
                        throw new JhookException( "列的索引不能相同" );
                    }
                    if (excelColumn.weight()) {
                        flag = true;
                        ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
                        Type type = parameterizedType.getActualTypeArguments()[0];
                        Class cl = (Class) type;
                        setExcelContext( cl, contextMap );
                    } else {
                        contextMap.put( excelColumn.columnIndex(), excelContext );
                    }
                }

            }

        }
        return flag;
    }
}
